/*
 * Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <arch_helpers.h>
#include <assert.h>
#include <debug.h>
#include <emmc.h>
#include <errno.h>
#include <firmware_image_package.h>
#include <io_block.h>
#include <io_driver.h>
#include <io_fip.h>
#include <io_memmap.h>
#include <io_storage.h>
#include <mmio.h>
#include <platform_def.h>
#include <semihosting.h>    /* For FOPEN_MODE_... */
#include <string.h>
#include "hikey_private.h"

#define EMMC_BLOCK_SHIFT            9

/* Page 1024, since only a few pages before 2048 are used as partition table */
#define SERIALNO_EMMC_OFFSET            (1024 * 512)

struct plat_io_policy {
    uintptr_t *dev_handle;
    uintptr_t image_spec;
    int (*check)(const uintptr_t spec);
};

static const io_dev_connector_t *emmc_dev_con;
static uintptr_t emmc_dev_handle;
static const io_dev_connector_t *fip_dev_con;
static uintptr_t fip_dev_handle;

static int check_emmc(const uintptr_t spec);
static int check_fip(const uintptr_t spec);

static const io_block_spec_t emmc_fip_spec = {
    .offset        = HIKEY_FIP_BASE,
    .length        = HIKEY_FIP_MAX_SIZE,
};

static const io_block_dev_spec_t emmc_dev_spec = {
    /* It's used as temp buffer in block driver. */
#if IMAGE_BL1
    .buffer        = {
        .offset    = HIKEY_BL1_MMC_DATA_BASE,
        .length    = HIKEY_BL1_MMC_DATA_SIZE,
    },
#else
    .buffer        = {
        .offset    = HIKEY_MMC_DATA_BASE,
        .length    = HIKEY_MMC_DATA_SIZE,
    },
#endif
    .ops        = {
        .read    = emmc_read_blocks,
        .write    = emmc_write_blocks,
    },
    .block_size    = EMMC_BLOCK_SIZE,
};

static const io_uuid_spec_t bl2_uuid_spec = {
    .uuid = UUID_TRUSTED_BOOT_FIRMWARE_BL2,
};

static const io_uuid_spec_t bl31_uuid_spec = {
    .uuid = UUID_EL3_RUNTIME_FIRMWARE_BL31,
};

static const io_uuid_spec_t bl33_uuid_spec = {
    .uuid = UUID_NON_TRUSTED_FIRMWARE_BL33,
};

static const io_uuid_spec_t scp_bl2_uuid_spec = {
    .uuid = UUID_SCP_FIRMWARE_SCP_BL2,
};

static const struct plat_io_policy policies[] = {
    [FIP_IMAGE_ID] = {
        &emmc_dev_handle,
        (uintptr_t)&emmc_fip_spec,
        check_emmc
    },
    [BL2_IMAGE_ID] = {
        &fip_dev_handle,
        (uintptr_t)&bl2_uuid_spec,
        check_fip
    },
    [SCP_BL2_IMAGE_ID] = {
        &fip_dev_handle,
        (uintptr_t)&scp_bl2_uuid_spec,
        check_fip
    },
    [BL31_IMAGE_ID] = {
        &fip_dev_handle,
        (uintptr_t)&bl31_uuid_spec,
        check_fip
    },
    [BL33_IMAGE_ID] = {
        &fip_dev_handle,
        (uintptr_t)&bl33_uuid_spec,
        check_fip
    }
};

static int check_emmc(const uintptr_t spec)
{
    int result;
    uintptr_t local_handle;

    result = io_dev_init(emmc_dev_handle, (uintptr_t)NULL);
    if (result == 0) {
        result = io_open(emmc_dev_handle, spec, &local_handle);
        if (result == 0)
            io_close(local_handle);
    }
    return result;
}

static int check_fip(const uintptr_t spec)
{
    int result;
    uintptr_t local_image_handle;

    /* See if a Firmware Image Package is available */
    result = io_dev_init(fip_dev_handle, (uintptr_t)FIP_IMAGE_ID);
    if (result == 0) {
        result = io_open(fip_dev_handle, spec, &local_image_handle);
        if (result == 0) {
            VERBOSE("Using FIP\n");
            io_close(local_image_handle);
        }
    }
    return result;
}

void hikey_io_setup(void)
{
    int result;

    result = register_io_dev_block(&emmc_dev_con);
    assert(result == 0);

    result = register_io_dev_fip(&fip_dev_con);
    assert(result == 0);

    result = io_dev_open(emmc_dev_con, (uintptr_t)&emmc_dev_spec,
                 &emmc_dev_handle);
    assert(result == 0);

    result = io_dev_open(fip_dev_con, (uintptr_t)NULL, &fip_dev_handle);
    assert(result == 0);

    /* Ignore improbable errors in release builds */
    (void)result;
}

/* Return an IO device handle and specification which can be used to access
 * an image. Use this to enforce platform load policy
 */
int plat_get_image_source(unsigned int image_id, uintptr_t *dev_handle,
              uintptr_t *image_spec)
{
    int result;
    const struct plat_io_policy *policy;

    assert(image_id < ARRAY_SIZE(policies));

    policy = &policies[image_id];
    result = policy->check(policy->image_spec);
    assert(result == 0);

    *image_spec = policy->image_spec;
    *dev_handle = *(policy->dev_handle);

    return result;
}
